package ch.simplix.android.flashlight;

import android.app.Service;
import android.content.Intent;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/**
 * This Service allows to easily access the camera-flash of an android device.
 * One can either use the typical flash on / off or a strobe-mode, where the
 * time during which the flash is on is as short as possible. This is useful to
 * do stroboscopic measurements. Do not expect too much of the strobe, as no
 * timing can really be guaranteed. It's an approximation.
 * 
 * @author SimpliX
 * 
 */
public class FlashLightService extends Service {
	public static final String TAG = "FlashLightService";
	public static final String FLASH_LIGHT_SERVICE = "ch.simplix.android.flashlight.FLASH_LIGHT_SERVICE";
	public static final String STROBE = "strobe";
	public static final String ON = "on";
	public static final String OFF = "off";
	public static final String DARK_DELAY = "darkDelay";

	private final LocalBinder localBinder = new LocalBinder();
	private Camera camera;
	private Parameters flashParameters;
	private Parameters darkParameters;
	private Strobe strobe;

	/**
	 * @see Binder
	 */
	public class LocalBinder extends Binder {
		public FlashLightService getService() {
			return FlashLightService.this;
		}
	}

	@Override
	public IBinder onBind(final Intent intent) {
		return this.localBinder;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		this.strobe = new Strobe();

	}

	@Override
	public int onStartCommand(final Intent intent, final int flags,
			final int startId) {
		if (intent.getExtras().containsKey(FlashLightService.STROBE)) {
			if (intent.getExtras().containsKey(FlashLightService.DARK_DELAY)) {
				this.strobe.setDelay(intent.getExtras().getInt(
						FlashLightService.DARK_DELAY));
			}
			if (intent.getExtras().containsKey(FlashLightService.ON)) {
				try {
					this.strobeOn();
				} catch (final Exception e) {
					Log.d(FlashLightService.TAG, "No Flash-Light available");
				}
			} else if (intent.getExtras().containsKey(FlashLightService.OFF)) {
				this.strobeOff();
			}
		} else if (intent.getExtras().containsKey(FlashLightService.ON)) {
			try {
				this.lightOn();
			} catch (final Exception e) {
				Log.d(FlashLightService.TAG, "No Flash-Light available");
			}
		} else if (intent.getExtras().containsKey(FlashLightService.OFF)) {
			this.lightOff();
		}
		return super.onStartCommand(intent, flags, startId);
	}

	private void setupFlashLight() throws Exception {

		if (this.camera != null) {
			return;
		}
		this.camera = Camera.open();
		this.camera.startPreview();
		this.flashParameters = this.camera.getParameters();
		this.flashParameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
		this.darkParameters = this.camera.getParameters();
		this.darkParameters.setFlashMode(Parameters.FLASH_MODE_OFF);

	}

	private void teardownFlashLight() {
		if (this.camera == null) {
			return;
		}
		this.camera.stopPreview();
		this.camera.release();
		this.camera = null;
		this.flashParameters = null;
		this.darkParameters = null;
	}

	public void lightOn() throws Exception {
		this.setupFlashLight();
		this.camera.setParameters(this.flashParameters);
	}

	public void strobeOn() throws Exception {
		this.setupFlashLight();
		this.strobe.on();
	}

	public void strobeOff() {
		this.strobe.off();
		this.teardownFlashLight();

	}

	public void lightOff() {
		try {
			this.strobeOff();
			this.camera.setParameters(this.flashParameters);
			this.teardownFlashLight();
		} catch (final Exception ex) {
		}
	}

	/**
	 * Everything has to be ready here!
	 */
	protected void strobe() {
		this.camera.setParameters(this.flashParameters);
		this.camera.setParameters(this.darkParameters);
	}

	@Override
	public void onDestroy() {
		this.lightOff();
		this.strobe = null;
		super.onDestroy();
	}

	class Strobe implements Runnable {

		private boolean isTimeToQuit;
		private volatile long delay;
		private Thread thread;

		public synchronized void on() {
			if ((this.thread != null) && this.thread.isAlive()) {
				return;
			}
			this.isTimeToQuit = false;
			this.thread = new Thread(this);
			this.thread.start();
		}

		public synchronized void off() {
			this.isTimeToQuit = true;
			try {
				this.thread.interrupt();
				this.thread.join();
				this.thread = null;
			} catch (final Exception ex) {
				//
			}
		}

		public synchronized void setDelay(final long delay) {
			this.delay = delay;
			try {
				this.thread.interrupt();
			} catch (final Exception ex) {
				//
			}

		}

		public synchronized long getDelay() {
			return this.delay;
		}

		@Override
		public void run() {
			while (!this.isTimeToQuit) {
				try {
					final long delay = this.delay; // Faster (possibly in the
													// CPU-Register than on the
													// heap)
					while (true) {// Double-Loop to be faster. (No variable
									// verification)
						Thread.sleep(delay);
						// strobe();
						// Just to be even faster! No additional method-call
						FlashLightService.this.camera
								.setParameters(FlashLightService.this.flashParameters);
						FlashLightService.this.camera
								.setParameters(FlashLightService.this.darkParameters);
					}
				} catch (final Exception e) {
					// That is fine, then let's have a look at the news
				}
			}
		}
	}

}
